package com.tom.demo.common.exception;

import com.tom.demo.common.enumeration.ExceptionEnum;
import com.tom.demo.common.enumeration.ResponseEnum;
import com.tom.demo.common.response.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.sql.SQLException;
import java.util.List;

/**
 * 全局异常处理器
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理可控异常  自定义异常
     * @param e
     * @return
     */
    @ExceptionHandler(CustomException.class)
    public Result customException(CustomException e,HttpServletRequest request){
        log.error("customException start, uri:{}, exception:{}, caused by: {}",request.getRequestURI(),e.getResponseEnum(),e.getResponseEnum().getMessage());
        return Result.error(e.getResponseEnum());
    }

    /**
     * 用户名或者密码错误异常会被它捕获，但是身份失效异常会被authenticationEntryPoint拦截到
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(value = AuthenticationException.class)
    public Result authenticationHandler(Exception e,HttpServletRequest request){
        log.error("用户名或者密码错误！AuthenticationException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
        return Result.error(ExceptionEnum.LOGIN_PASSWORD_ERROR.getMessage(),ExceptionEnum.LOGIN_PASSWORD_ERROR.getCode());
    }

    /**
     * 访问接口，接口上面使用了@PreAuthorize(value = "hasAuthority('system:user:list')") 如果权限不足，会到这个地方处理
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public Result accessDeniedHandler(Exception e,HttpServletRequest request){
        log.error("用户访问接口权限不足！AccessDeniedException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
        return Result.error("权限不足",HttpStatus.FORBIDDEN.value());
    }

    /**
     * 全局系统异常，未知异常统一处理
     */
    @ExceptionHandler(value = Exception.class)
    public Result errorHandler(Exception e, HttpServletRequest request) {
        if (e instanceof NoHandlerFoundException) {
            log.error("NoHandlerFoundException error start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error("请求找不到请求资源控制器",HttpStatus.NOT_FOUND.value());
        } else if (e instanceof MissingServletRequestParameterException) {
            log.error("MissingServletRequestParameterException error start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.PARAM_REQUIRE);
        } else if (e instanceof TypeMismatchException) {
            log.error("TypeMismatchException error start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.PARAM_TYPE);
        } else if(e instanceof HttpMessageNotReadableException){
            log.error("HttpMessageNotReadableException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.JSON_SERIALIZE_OBJECT);
        } else if(e instanceof HttpMediaTypeNotSupportedException){
            log.error("HttpMediaTypeNotSupportedException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.PARAM_CONTENT_TYPE_NO_SUPPORT);
        } else if(e instanceof HttpMessageNotWritableException){
            log.error("HttpMessageNotWritableException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.OBJECT_SERIALIZE_JSON);
        } else if(e instanceof HttpRequestMethodNotSupportedException){
            log.error("HttpRequestMethodNotSupportedException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.REQUEST_METHOD_NOT_SUPPORTED);
        } else if(e instanceof MissingPathVariableException){
            log.error("MissingPathVariableException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.NOT_PATH_PARAM_DETECTED);
        } else if(e instanceof MethodArgumentNotValidException){
            String errorMsg = getErrorMsg((MethodArgumentNotValidException) e, request);
            return Result.error(errorMsg);
        } else if(e instanceof BindException){
            log.error("BindException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.REQUEST_PARAM_BINDING_ERROR);
        } else if (e instanceof SQLException) {
            log.error("SQLException error start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.DATABASE_OPERATION_FAILED);
        } else if (e instanceof MaxUploadSizeExceededException) {
            log.error("MaxUploadSizeExceededException error start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.FILE_SIZE_EXCEEDS_LIMIT);
        } else if (e instanceof SocketTimeoutException) {
            log.error("SocketTimeoutException error start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.SOCKET_CONNECTION_TIMED_OUT);
        } else if (e instanceof SocketException) {
            log.error("SocketException error start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.SOCKET_SERVICE_CONNECTION_FAILED);
        } else if (e instanceof IOException) {
            log.error("IOException error start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.FILE_READ_WRITE_ERROR);
        }else if(e instanceof RuntimeException){
            log.error("RuntimeException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error(ResponseEnum.SERVER_ERROR);
        }  else {
            log.error("Exception error start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            return Result.error("系统错误");
        }
    }

    @NotNull
    private static String getErrorMsg(MethodArgumentNotValidException e, HttpServletRequest request) {
        BindingResult result = e.getBindingResult();
        StringBuilder stringBuilder = new StringBuilder("参数校验失败:");
        List<ObjectError> errors = result.getAllErrors();
        errors.forEach(p -> {
            FieldError fieldError = (FieldError) p;
            log.error("MethodArgumentNotValidException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
            stringBuilder.append(fieldError.getDefaultMessage()).append(";");
        });
        return stringBuilder.toString();
    }

}